package com.xxl.job.admin.core.trigger;

import com.xxl.job.admin.core.conf.XxlJobAdminConfig;
import com.xxl.job.admin.core.model.XxlJobGroup;
import com.xxl.job.admin.core.model.XxlJobInfo;
import com.xxl.job.admin.core.model.XxlJobLog;
import com.xxl.job.admin.core.route.ExecutorRouteStrategyEnum;
import com.xxl.job.admin.core.scheduler.XxlJobScheduler;
import com.xxl.job.admin.core.thread.JobTriggerPoolHelper;
import com.xxl.job.admin.core.util.I18nUtil;
import com.xxl.job.admin.core.util.SqlSessionUtil;
import com.xxl.job.core.biz.ExecutorBiz;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.biz.model.TriggerParam;
import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
import com.xxl.job.core.util.DateUtil;
import com.xxl.job.core.util.IpUtil;
import com.xxl.job.core.util.ThrowableUtil;
import org.apache.ibatis.session.SqlSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * xxl-job trigger
 * Created by xuxueli on 17/7/13.
 */
public class XxlJobTrigger {
    private static Logger logger = LoggerFactory.getLogger(XxlJobTrigger.class);

    private static ThreadPoolExecutor  xxlJobLogInitThreadPool = new ThreadPoolExecutor(5, 20, 1, TimeUnit.HOURS,
            new LinkedBlockingQueue<>(120),
            r -> new Thread(r, "xxl-job, admin XxlJobTrigger-xxlJobLogThreadPool-" + r.hashCode()),
            (r, executor) -> {
            r.run();
        logger.warn(">>>>>>>>>>> xxl-job, init xxlJobLog too fast, match threadpool rejected handler(run now).");
    });

    private static ThreadPoolExecutor  xxlJobLogUpdateThreadPool = new ThreadPoolExecutor(5, 20, 1, TimeUnit.HOURS,
            new LinkedBlockingQueue<>(1000),
            r -> new Thread(r, "xxl-job, admin XxlJobTrigger-xxlJobLogThreadPool-" + r.hashCode()),
            (r, executor) -> {
                r.run();
                logger.warn(">>>>>>>>>>> xxl-job, init xxlJobLog too fast, match threadpool rejected handler(run now).");
            });

    private static CopyOnWriteArrayList copyOnWriteArrayList = new CopyOnWriteArrayList();



    /**
     * trigger job
     *
     * @param jobId
     * @param triggerType
     * @param failRetryCount        >=0: use this param
     *                              <0: use param from job info config
     * @param executorShardingParam
     * @param executorParam         null: use job param
     *                              not null: cover job param
     * @param addressList           null: use executor addressList
     *                              not null: cover
     */
    public static void trigger(long jobId,
                               TriggerTypeEnum triggerType,
                               int failRetryCount,
                               String executorShardingParam,
                               String executorParam,
                               String addressList) {
        logger.debug("jobId = {}, triggerType = {}, addressList = {}", jobId, triggerType.getTitle(), addressList);

        // load data
        XxlJobInfo jobInfo = XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().loadById(jobId);
        if (jobInfo == null) {
            logger.warn(">>>>>>>>>>>> trigger fail, jobId invalid，jobId={}", jobId);
            return;
        }
        if (executorParam != null) {
            jobInfo.setExecutorParam(executorParam);
        }
        int finalFailRetryCount = failRetryCount >= 0 ? failRetryCount : jobInfo.getExecutorFailRetryCount();
        XxlJobGroup group = XxlJobAdminConfig.getAdminConfig().getXxlJobGroupDao().load(jobInfo.getJobGroup());

        // cover addressList
        if (addressList != null && addressList.trim().length() > 0) {
            group.setAddressType(1);
            group.setAddressList(addressList.trim());
        }

        // sharding param
        int[] shardingParam = null;
        if (executorShardingParam != null) {
            String[] shardingArr = executorShardingParam.split("/");
            if (shardingArr.length == 2 && isNumeric(shardingArr[0]) && isNumeric(shardingArr[1])) {
                shardingParam = new int[2];
                shardingParam[0] = Integer.valueOf(shardingArr[0]);
                shardingParam[1] = Integer.valueOf(shardingArr[1]);
            }
        }
        if (ExecutorRouteStrategyEnum.SHARDING_BROADCAST == ExecutorRouteStrategyEnum.match(jobInfo.getExecutorRouteStrategy(), null)
                && group.getRegistryList() != null && !group.getRegistryList().isEmpty()
                && shardingParam == null) {
            for (int i = 0; i < group.getRegistryList().size(); i++) {
                processTrigger(group, jobInfo, finalFailRetryCount, triggerType, i, group.getRegistryList().size());
            }
        } else {
            if (shardingParam == null) {
                shardingParam = new int[]{0, 1};
            }
            processTrigger(group, jobInfo, finalFailRetryCount, triggerType, shardingParam[0], shardingParam[1]);
        }
    }


    /**
     * trigger job batch
     * @param jobInfoMap
     * @param triggerTime
     */
    public static void triggerBatch(Map<Long, List<XxlJobInfo>> jobInfoMap, Date triggerTime) {

        if(jobInfoMap.isEmpty()){
            return;
        }
        long startTime = System.currentTimeMillis(),endTime;

        List<Long> xxlJobGroupIdList = jobInfoMap.keySet().stream().collect(Collectors.toList());

        List<XxlJobGroup> xxlJobGroupList = XxlJobAdminConfig.getAdminConfig().getXxlJobGroupDao().findIds(xxlJobGroupIdList);
        Map<Long, XxlJobGroup> xxlJobGroupMap = xxlJobGroupList.stream().collect(Collectors.toMap(XxlJobGroup::getId,o->o));
        for (Map.Entry<Long, List<XxlJobInfo>> entry : jobInfoMap.entrySet()){
            Long xxlJobGroupId = entry.getKey();
            XxlJobGroup group = xxlJobGroupMap.get(xxlJobGroupId);

            List<XxlJobInfo> xxlJobInfoList = entry.getValue();

            List<XxlJobInfo> xxlJobInfoListFast = xxlJobInfoList.stream().filter(xxlJobInfo -> !xxlJobInfo.needCheckExecutorStatus()).collect(Collectors.toList());
            processTriggerBatch(group, xxlJobInfoListFast, triggerTime);
            //需要访问执行器的走原来的方式，批量处理中不阻塞主流程
            List<XxlJobInfo>  xxlJobInfoListSlow = xxlJobInfoList.stream().filter(xxlJobInfo -> xxlJobInfo.needCheckExecutorStatus()).collect(Collectors.toList());
            if(!xxlJobInfoListSlow.isEmpty()){
                for (XxlJobInfo xxlJobInfo : xxlJobInfoListSlow){
                    JobTriggerPoolHelper.trigger(xxlJobInfo.getId(),TriggerTypeEnum.CRON, -1, null, null, null);
                }
            }
        }
        endTime = System.currentTimeMillis();

        logger.info("schedule useTime = {}ms", (endTime - startTime));

    }




    private static boolean isNumeric(String str) {
        try {
            int result = Integer.valueOf(str);
            return true;
        } catch (NumberFormatException e) {
            return false;
        }
    }

    /**
     * @param group               job group, registry list may be empty
     * @param jobInfo
     * @param finalFailRetryCount
     * @param triggerType
     * @param index               sharding index
     * @param total               sharding index
     */
    private static void processTrigger(XxlJobGroup group, XxlJobInfo jobInfo, int finalFailRetryCount, TriggerTypeEnum triggerType, int index, int total) {

        // param
        // block strategy
        ExecutorBlockStrategyEnum blockStrategy = ExecutorBlockStrategyEnum.match(jobInfo.getExecutorBlockStrategy(), ExecutorBlockStrategyEnum.SERIAL_EXECUTION);
        // route strategy
        ExecutorRouteStrategyEnum executorRouteStrategyEnum = ExecutorRouteStrategyEnum.match(jobInfo.getExecutorRouteStrategy(), null);
        String shardingParam = (ExecutorRouteStrategyEnum.SHARDING_BROADCAST == executorRouteStrategyEnum) ? String.valueOf(index).concat("/").concat(String.valueOf(total)) : null;

        // 1、save log-id
        XxlJobLog jobLog = new XxlJobLog();
        jobLog.setJobGroup(jobInfo.getJobGroup());
        jobLog.setJobId(jobInfo.getId());
        jobLog.setTriggerTime(new Date());
        jobLog.setHandleCode(200);
        jobLog.setUuid(UUID.randomUUID().toString());
        XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().save(jobLog);
        logger.debug(">>>>>>>>>>> xxl-job trigger start, jobId:{}", jobLog.getId());

        // 2、init trigger-param
        TriggerParam triggerParam = new TriggerParam();
        triggerParam.setJobId(jobInfo.getId());
        triggerParam.setExecutorHandler(jobInfo.getExecutorHandler());
        triggerParam.setExecutorParams(jobInfo.getExecutorParam());
        triggerParam.setExecutorBlockStrategy(jobInfo.getExecutorBlockStrategy());
        triggerParam.setExecutorTimeout(jobInfo.getExecutorTimeout());
        triggerParam.setLogId(jobLog.getId());
        triggerParam.setLogDateTime(jobLog.getTriggerTime().getTime());
        triggerParam.setGlueType(jobInfo.getGlueType());
        triggerParam.setGlueSource(jobInfo.getGlueSource());
        triggerParam.setGlueUpdatetime(jobInfo.getGlueUpdatetime().getTime());
        triggerParam.setBroadcastIndex(index);
        triggerParam.setBroadcastTotal(total);

        // 3、init address
        String address = null;
        ReturnT<String> routeAddressResult = null;
        if (group.getRegistryList() != null && !group.getRegistryList().isEmpty()) {
            if (ExecutorRouteStrategyEnum.SHARDING_BROADCAST == executorRouteStrategyEnum) {
                if (index < group.getRegistryList().size()) {
                    address = group.getRegistryList().get(index);
                } else {
                    address = group.getRegistryList().get(0);
                }
            } else {
                routeAddressResult = executorRouteStrategyEnum.getRouter().route(triggerParam.getJobId(), group.getRegistryList());
                if (routeAddressResult.getCode() == ReturnT.SUCCESS_CODE) {
                    address = routeAddressResult.getContent();
                }
            }
        } else {
            routeAddressResult = new ReturnT<>(ReturnT.FAIL_CODE, I18nUtil.getString("jobconf_trigger_address_empty"));
        }

        // 4、trigger remote executor
        ReturnT<String> triggerResult = null;
        if (address != null) {
            triggerResult = runExecutor(triggerParam, address);
        } else {
            triggerResult = new ReturnT<>(ReturnT.FAIL_CODE, null);
        }

        // 5、collection trigger info
        StringBuffer triggerMsgSb = new StringBuffer();
        triggerMsgSb.append(I18nUtil.getString("jobconf_trigger_type")).append("：").append(triggerType.getTitle());
        triggerMsgSb.append("<br>").append(I18nUtil.getString("jobconf_trigger_admin_adress")).append("：").append(IpUtil.getIp());
        triggerMsgSb.append("<br>").append(I18nUtil.getString("jobconf_trigger_exe_regtype")).append("：")
                .append((group.getAddressType() == 0) ? I18nUtil.getString("jobgroup_field_addressType_0") : I18nUtil.getString("jobgroup_field_addressType_1"));
        triggerMsgSb.append("<br>").append(I18nUtil.getString("jobconf_trigger_exe_regaddress")).append("：").append(group.getRegistryList());
        triggerMsgSb.append("<br>").append(I18nUtil.getString("jobinfo_field_executorRouteStrategy")).append("：").append(executorRouteStrategyEnum.getTitle());
        if (shardingParam != null) {
            triggerMsgSb.append("(" + shardingParam + ")");
        }
        triggerMsgSb.append("<br>").append(I18nUtil.getString("jobinfo_field_executorBlockStrategy")).append("：").append(blockStrategy.getTitle());
        triggerMsgSb.append("<br>").append(I18nUtil.getString("jobinfo_field_timeout")).append("：").append(jobInfo.getExecutorTimeout());
        triggerMsgSb.append("<br>").append(I18nUtil.getString("jobinfo_field_executorFailRetryCount")).append("：").append(finalFailRetryCount);

        triggerMsgSb.append("<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>" + I18nUtil.getString("jobconf_trigger_run") + "<<<<<<<<<<< </span><br>")
                .append((routeAddressResult != null && routeAddressResult.getMsg() != null) ? routeAddressResult.getMsg() + "<br><br>" : "").append(triggerResult.getMsg() != null ? triggerResult.getMsg() : "");

        // 6、save log trigger-info
        jobLog.setExecutorAddress(address);
        jobLog.setExecutorHandler(jobInfo.getExecutorHandler());
        jobLog.setExecutorParam(jobInfo.getExecutorParam());
        jobLog.setExecutorShardingParam(shardingParam);
        jobLog.setExecutorFailRetryCount(finalFailRetryCount);
        jobLog.setTriggerCode(triggerResult.getCode());
        jobLog.setTriggerMsg(triggerMsgSb.toString());
        XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().updateTriggerInfo(jobLog);

        logger.debug(">>>>>>>>>>> xxl-job trigger end, jobId:{}", jobLog.getId());
    }


    /**
     * 批量处理触发器
     * @param group
     * @param jobInfoList
     * @param triggerTime
     */
    private static void processTriggerBatch(XxlJobGroup group, List<XxlJobInfo> jobInfoList,  Date triggerTime) {

        if(jobInfoList.isEmpty()){
            return;
        }
        if(group.getRegistryList() == null || group.getRegistryList().size() == 0){
            logger.info("group registry address is null");
            insertFailedTriggerLog(jobInfoList);
            return;
        }

        Map<String,List<XxlJobInfo>> jobInfoAddressMap = new HashMap<>(128);


        //ExecutorRouteBusyover
        //ExecutorRouteFailover

        List<XxlJobInfo> failedRouteAddressJobList = new ArrayList<>();

        for (XxlJobInfo xxlJobInfo : jobInfoList){
            ExecutorRouteStrategyEnum executorRouteStrategyEnum = ExecutorRouteStrategyEnum.match(xxlJobInfo.getExecutorRouteStrategy(), null);
            ReturnT<String> routeAddressResult = executorRouteStrategyEnum.getRouter().route(xxlJobInfo.getId(), group.getRegistryList());
            String address = null;
            if (routeAddressResult.getCode() == ReturnT.SUCCESS_CODE) {
                address = routeAddressResult.getContent();
            }
            if(address != null){
                //分片广播
                if(executorRouteStrategyEnum.getTitle().equals(ExecutorRouteStrategyEnum.SHARDING_BROADCAST.getTitle())){
                    List<String> registryList = group.getRegistryList();

                    int i = 0;
                    for (String registryUrl : registryList) {
                        List<XxlJobInfo> xxlJobInfoListInAddress = jobInfoAddressMap.get(registryUrl);
                        if(xxlJobInfoListInAddress == null){
                            xxlJobInfoListInAddress = new ArrayList<>();
                        }
                        xxlJobInfo.setBroadcastIndex(i);
                        xxlJobInfo.setBroadcastTotal(registryList.size());
                        i++;
                        xxlJobInfoListInAddress.add(xxlJobInfo.copyMyself());
                        jobInfoAddressMap.put(address, xxlJobInfoListInAddress);
                    }
                    continue;
                }

                List<XxlJobInfo> xxlJobInfoList = jobInfoAddressMap.get(address);
                if(xxlJobInfoList == null){
                    xxlJobInfoList = new ArrayList<>();
                }
                xxlJobInfo.setBroadcastIndex(0);
                xxlJobInfo.setBroadcastTotal(1);
                xxlJobInfoList.add(xxlJobInfo);
                jobInfoAddressMap.put(address, xxlJobInfoList);
            }
            else {
                xxlJobInfo.setRouteAddressResult(routeAddressResult);
                failedRouteAddressJobList.add(xxlJobInfo);
            }
        }

        //按调度地址分组并进行调度
        for (Map.Entry<String,List<XxlJobInfo>> entry : jobInfoAddressMap.entrySet()){
            String address = entry.getKey();
            List<XxlJobInfo> xxlJobInfoList = entry.getValue();
            //1.初始化 xxl job log and trigger param
            List<TriggerParam> triggerParamList = initTriggerParams(entry.getValue(), address, triggerTime);
            //2.批量执行
            ReturnT<String> triggerBatchResult = runExecutorBatch(triggerParamList, address);
            //3.整理触发日志
            updateXxlJobLog(group, xxlJobInfoList, triggerParamList, triggerBatchResult);
        }


        if(failedRouteAddressJobList.size() > 0){
            insertFailedTriggerLog(failedRouteAddressJobList);
        }
    }


    /**
     * run executor batch
     *
     * @param triggerParamList
     * @param address
     * @return
     */
    public static ReturnT<String> runExecutorBatch(List<TriggerParam> triggerParamList, String address) {
        ReturnT<String> runResult = null;
        try {
            ExecutorBiz executorBizClient = XxlJobScheduler.getExecutorBiz(address);
            String key = address.replace("run/","").replace("run","");
            String cookie = XxlJobAdminConfig.getAdminConfig().getRegistCookie(key);
            runResult = executorBizClient.runBatch(triggerParamList, cookie);

        } catch (Exception e) {
            logger.error(">>>>>>>>>>> xxl-job trigger error, please check if the executor[{}] is running.", address, e);
            runResult = new ReturnT<>(ReturnT.FAIL_CODE, ThrowableUtil.toString(e));
        }

        StringBuffer runResultSB = new StringBuffer(I18nUtil.getString("jobconf_trigger_run") + "：");
        runResultSB.append("<br>address：").append(address);
        if(runResult != null){
            runResultSB.append("<br>code：").append(runResult.getCode());
            runResultSB.append("<br>msg：").append(runResult.getMsg());
            runResult.setMsg(runResultSB.toString());
        }



        return runResult;
    }




    /**
     * run executor
     *
     * @param triggerParam
     * @param address
     * @return
     */
    public static ReturnT<String> runExecutor(TriggerParam triggerParam, String address) {
        ReturnT<String> runResult = null;
        try {
            ExecutorBiz executorBizClient = XxlJobScheduler.getExecutorBiz(address);

            String key = address.replace("run/","").replace("run","");
            String cookie = XxlJobAdminConfig.getAdminConfig().getRegistCookie(key);
            //runResult = executorBizClient.run(triggerParam, cookie);

            runResult = executorBizClient.runPool(triggerParam, cookie);

        } catch (Exception e) {
            logger.error(">>>>>>>>>>> xxl-job trigger error, please check if the executor[{}] is running.", address, e);
            runResult = new ReturnT<>(ReturnT.FAIL_CODE, ThrowableUtil.toString(e));
        }

        StringBuffer runResultSB = new StringBuffer(I18nUtil.getString("jobconf_trigger_run") + "：");
        runResultSB.append("<br>address：").append(address);
        runResultSB.append("<br>code：").append(runResult.getCode());
        runResultSB.append("<br>msg：").append(runResult.getMsg());

        runResult.setMsg(runResultSB.toString());
        return runResult;
    }

    /**
     * 初始化并保存xxljog 返回TriggerParam 列表
     * @param xxlJobInfoList
     * @param address
     * @param triggerTime
     * @return
     */
    private static List<TriggerParam> initTriggerParams(List<XxlJobInfo> xxlJobInfoList, String address, Date triggerTime){
        // 1、save log-id

        List<TriggerParam> triggerParams = new ArrayList<>();
        List<XxlJobLog> xxlJobLogs = new ArrayList<>();

        for (XxlJobInfo xxlJobInfo : xxlJobInfoList){
            XxlJobLog jobLog = new XxlJobLog();

            jobLog.setJobGroup(xxlJobInfo.getJobGroup());
            jobLog.setJobId(xxlJobInfo.getId());
            jobLog.setTriggerTime(triggerTime);
            jobLog.setTriggerCode(ReturnT.SUCCESS.getCode());
            jobLog.setTriggerMsg("");
            jobLog.setHandleCode(ReturnT.SUCCESS.getCode());
            jobLog.setUuid(UUID.randomUUID().toString());
            jobLog.setExecutorAddress(address);
            jobLog.setExecutorHandler(xxlJobInfo.getExecutorHandler());
            jobLog.setExecutorParam(xxlJobInfo.getExecutorParam());
            xxlJobLogs.add(jobLog);

            // 2、init trigger-param
            TriggerParam triggerParam = new TriggerParam();
            triggerParam.setJobId(xxlJobInfo.getId());
            triggerParam.setExecutorHandler(xxlJobInfo.getExecutorHandler());
            triggerParam.setExecutorParams(xxlJobInfo.getExecutorParam());
            triggerParam.setExecutorBlockStrategy(xxlJobInfo.getExecutorBlockStrategy());
            triggerParam.setExecutorTimeout(xxlJobInfo.getExecutorTimeout());
            triggerParam.setLogUUID(jobLog.getUuid());
            triggerParam.setLogDateTime(jobLog.getTriggerTime().getTime());
            triggerParam.setGlueType(xxlJobInfo.getGlueType());
            triggerParam.setGlueSource(xxlJobInfo.getGlueSource());
            triggerParam.setGlueUpdatetime(xxlJobInfo.getGlueUpdatetime().getTime());
            triggerParam.setBroadcastIndex(xxlJobInfo.getBroadcastIndex());
            triggerParam.setBroadcastTotal(xxlJobInfo.getBroadcastTotal());
            triggerParams.add(triggerParam);
        }
        //todo insert xxljob log async
        insertMultBatch(xxlJobLogs);
        return triggerParams;

    }

    /**
     * 更新触发日志
     * @param xxlJobGroup
     * @param xxlJobInfoList
     * @param triggerParamList
     * @param triggerBatchResult
     */
    private static void updateXxlJobLog(XxlJobGroup xxlJobGroup,List<XxlJobInfo> xxlJobInfoList, List<TriggerParam> triggerParamList, ReturnT<String> triggerBatchResult){
        Map<Long,XxlJobInfo> xxlJobInfoMap = xxlJobInfoList.stream().collect(Collectors.toMap(XxlJobInfo::getId,o -> o));
        List<XxlJobLog> xxlJobLogList = new ArrayList<>();
        for (TriggerParam triggerParam : triggerParamList){
            String triggerMsg = buildTriggerMsg(xxlJobGroup, xxlJobInfoMap.get(triggerParam.getJobId()), triggerBatchResult);
            XxlJobLog xxlJobLog = new XxlJobLog();
            xxlJobLog.setTriggerMsg(triggerMsg);
            xxlJobLog.setTriggerCode(triggerBatchResult.getCode());
            xxlJobLog.setUuid(triggerParam.getLogUUID());
            xxlJobLogList.add(xxlJobLog);
        }


        //批量修改
        if(xxlJobLogList.size() > 1000){
            int x = xxlJobLogList.size() / 1000;
            int page = xxlJobLogList.size() % 1000 == 0 ? x : x + 1;
            for (int i = 0;i < page ;i ++){
                List<XxlJobLog> pageList;
                if(i == page - 1){
                    pageList  = xxlJobLogList.subList(i * 1000,xxlJobLogList.size());
                }else {
                    pageList = xxlJobLogList.subList(i * 1000,(i + 1 ) * 1000);
                }
                updateTriggerInfoBatch(pageList);
            }
        }else {
            updateTriggerInfoBatch(xxlJobLogList);
        }



    }


    /**
     * 构建触发日志描述
     * @param xxlJobGroup
     * @param xxlJobInfo
     * @param triggerBatchResult
     * @return
     */
    private static String buildTriggerMsg(XxlJobGroup xxlJobGroup, XxlJobInfo xxlJobInfo, ReturnT triggerBatchResult){
        StringBuffer triggerMsgSb = new StringBuffer();
        triggerMsgSb.append(I18nUtil.getString("jobconf_trigger_type")).append("：").append(TriggerTypeEnum.CRON.getTitle());
        triggerMsgSb.append("<br>").append(I18nUtil.getString("jobconf_trigger_admin_adress")).append("：").append(IpUtil.getIp());
        triggerMsgSb.append("<br>").append(I18nUtil.getString("jobconf_trigger_exe_regtype")).append("：")
                .append((xxlJobGroup.getAddressType() == 0) ? I18nUtil.getString("jobgroup_field_addressType_0") : I18nUtil.getString("jobgroup_field_addressType_1"));
        triggerMsgSb.append("<br>").append(I18nUtil.getString("jobconf_trigger_exe_regaddress")).append("：").append(xxlJobGroup.getRegistryList());
        triggerMsgSb.append("<br>").append(I18nUtil.getString("jobinfo_field_executorRouteStrategy")).append("：").append(xxlJobInfo.getExecutorRouteStrategy());
        triggerMsgSb.append("<br>").append(I18nUtil.getString("jobinfo_field_executorBlockStrategy")).append("：").append(xxlJobInfo.getExecutorBlockStrategy());
        triggerMsgSb.append("<br>").append(I18nUtil.getString("jobinfo_field_timeout")).append("：").append(xxlJobInfo.getExecutorTimeout());
        triggerMsgSb.append("<br><br><span style=\"color:#00c0ef;\" > >>>>>>>>>>>" + I18nUtil.getString("jobconf_trigger_run") + "<<<<<<<<<<< </span><br>")
                .append(triggerBatchResult.getMsg() != null ? triggerBatchResult.getMsg() : "");
        return triggerMsgSb.toString();
    }

    /**
     * 插入调度失败的调度日志
     * @param xxlJobInfoList
     */
    private static void insertFailedTriggerLog(List<XxlJobInfo> xxlJobInfoList){
        if(xxlJobInfoList.isEmpty()){
            return;
        }
        List<XxlJobLog> xxlJobLogList = new ArrayList<>();
        for (XxlJobInfo xxlJobInfo : xxlJobInfoList){
            XxlJobLog xxlJobLog = new XxlJobLog();

            xxlJobLog.setUuid("");
            xxlJobLog.setJobId(xxlJobInfo.getId());
            xxlJobLog.setJobGroup(xxlJobInfo.getJobGroup());
            xxlJobLog.setHandleCode(ReturnT.FAIL_CODE);

            xxlJobLog.setExecutorAddress("");
            xxlJobLog.setExecutorHandler(xxlJobInfo.getExecutorHandler());
            xxlJobLog.setExecutorParam(xxlJobInfo.getExecutorParam());
            xxlJobLog.setTriggerCode(ReturnT.FAIL_CODE);
            xxlJobLog.setTriggerMsg("find route address failed.executorAddress is null");
            xxlJobLog.setTriggerTime(new Date());
            xxlJobLogList.add(xxlJobLog);
        }

        insertMultBatch(xxlJobLogList);

    }


    /**
     * 多次批量插入
     * @param xxlJobLogList
     */
    private static void insertMultBatch( List<XxlJobLog> xxlJobLogList){
        logger.debug("insertMultBatch.xxlJobLogList = {}", xxlJobLogList.size());
        //批量插入
        if(xxlJobLogList.size() > 1000){
            int x = xxlJobLogList.size() / 1000;
            int page = xxlJobLogList.size() % 1000 == 0 ? x : x + 1;
            for (int i = 0;i < page ;i ++){
                List<XxlJobLog> pageList;
                if(i == page - 1){
                    pageList  = xxlJobLogList.subList(i * 1000,xxlJobLogList.size());
                }else {
                    pageList = xxlJobLogList.subList(i * 1000,(i + 1 ) * 1000);
                }
                insertXxlJobLogBatch(pageList);
            }
        }else {
            insertXxlJobLogBatch(xxlJobLogList);
        }
    }

    /**
     * 批量修改
     * @param xxlJobLogList
     */
    private static void updateTriggerInfoBatch(List<XxlJobLog> xxlJobLogList){

        xxlJobLogUpdateThreadPool.execute(()->{
            try {
                Thread.sleep(200L);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            SqlSession session = SqlSessionUtil.createSqlBatchSession();
            String updateMethodStatement = "com.xxl.job.admin.dao.XxlJobLogDao.updateTriggerInfoV2";
            for (XxlJobLog xxlJobLog : xxlJobLogList){
                int x = session.update(updateMethodStatement, xxlJobLog);
                if (x == 0){
                    copyOnWriteArrayList.add(xxlJobLog);
                }
            }
            SqlSessionUtil.flush(session);
        });

    }

    /**
     * 批量插入
     * @param xxlJobLogList
     */
    private static void insertXxlJobLogBatch(List<XxlJobLog> xxlJobLogList){
        xxlJobLogInitThreadPool.execute(()->{
            String insertMethodStatement = "com.xxl.job.admin.dao.XxlJobLogDao.insertV2";
            SqlSession session = SqlSessionUtil.createSqlBatchSession();
            for (XxlJobLog xxlJobLog : xxlJobLogList){
                session.insert(insertMethodStatement, xxlJobLog);
            }
            SqlSessionUtil.flush(session);
        });

    }





}
